package logique.solveur;

import java.util.HashMap;
import java.util.Vector;
import java.util.Map.Entry;

import logique.outils.Chrono;
import logique.programme.Contrainte;
import logique.programme.FonctionEconomique;
import logique.programme.ProgrammeLineaire;
import logique.programme.Terme;
import logique.programme.Vecteur;
import logique.programme.FonctionEconomique.SensOptimisation;
import logique.solution.Affectation;
import logique.solution.Solution;
import logique.solution.Variable;
import logique.solution.VariableReelle;
import lpsolve.LpSolve;
import lpsolve.LpSolveException;

public class Lpsolve implements Solveur, SolveurInitial, SolveurOptimisation {

	// Attributes //
	private static Lpsolve singleton;
	private Chrono chrono;
	public static long dureeOptimisation = 0;
	private LpSolve lpSolver;
	private int[] colno;
	private double[] row;
	private Vector<Variable> variablesProgramme;
	private ProgrammeLineaire pl;


	// Methods //
	private Lpsolve() {
		super();

		// Création du chrono qui va calculer la durée d'optimisation
		chrono = new Chrono();
		chrono.start();
	}

	public static Lpsolve getInstance() {
		if (singleton == null) 
			singleton = new Lpsolve();
		return singleton;
	}

	public void addContraintes() {
		Vector<Contrainte> contraintes = pl.getContraintes();
		int operateur = -1;
		int j = 0;
		for (Contrainte contrainte : contraintes) {
			// Ajout d'une contrainte
			System.out.println("contrainte");
			System.out.println(" operateur : " + contrainte.getOperateur());
			System.out.println(" termeConstant : " + contrainte.getTermeConstant());

			j = 0;
			Vector<Terme> termes = contrainte.getListeTermes();
			colno = new int[termes.size()];
			row = new double[termes.size()];
			for (Terme terme : termes) {
				// Ajout d'un terme dans la contrainte
				System.out.println(" terme : " + terme.getCoeff() + " * " + terme.getVariable().getNomVecteur() + terme.getVariable().getIndex());

				colno[j] = variablesProgramme.indexOf(terme.getVariable())+1;
				System.out.println(" colno[j] = " + colno[j]);
				row[j++] = terme.getCoeff();
			}

			// Ajout de l'opérateur et de la valeur constante
			if (contrainte.getOperateur().equals("<=")) {
				operateur = LpSolve.LE;
			} else if (contrainte.getOperateur().equals(">=")) {
				operateur = LpSolve.GE;
			} else if (contrainte.getOperateur().equals("==")) {
				operateur = LpSolve.EQ;
			} else System.out.println("Impossible de reconnaitre l'opérateur");

			// On crée la contrainte
			try {
				lpSolver.addConstraintex(j, row, colno, operateur, contrainte.getTermeConstant());
				System.out.print("--- Contrainte " + j + " * ");
				for (int i = 0; i < row.length; i++) {
					System.out.print(row[i] + " " + colno[i] + " * ");
				}
				System.out.println(operateur + " " + contrainte.getTermeConstant());
			} catch (LpSolveException e) {
				e.printStackTrace();
			}
		}
	}

	public void addVariableEntiere(Variable v) {

	}

	public void addVariableReelle(Variable v) {

	}

	public void addObjectif() {
		Vector<Terme> termesFonctionEco = pl.getFonctionEconomique().getListeTermes();
		HashMap<Integer, Double> colnoMap = new HashMap<Integer, Double>();
		for (Terme terme : termesFonctionEco) {
			int indice = variablesProgramme.indexOf(terme.getVariable())+1;
			if (colnoMap.get(indice) != null) {
				colnoMap.put(indice, colnoMap.get(indice) + terme.getCoeff());
			}
			else {
				colnoMap.put(indice, terme.getCoeff());
			}
		}
		colno = new int[colnoMap.size()];
		row = new double[colnoMap.size()];
		int f=0;
		for (Entry<Integer, Double> entry : colnoMap.entrySet()) {
			colno[f] = entry.getKey();
			row[f] = entry.getValue();
			f++;
		}

		try {
			System.out.print("--- Objectif " + row.length + " * ");
			for (int i = 0; i < row.length; i++) {
				System.out.print(row[i] + " " + colno[i] + " * ");
			}
			System.out.println();
			lpSolver.setObjFnex(row.length, row, colno);
		} catch (LpSolveException e) {
			e.printStackTrace();
		}

		// On fixe la direction de l'optimisation
		if (pl.getFonctionEconomique().getOptimisation().equals(SensOptimisation.MIN)) {
			lpSolver.setMinim();
		}
		else {
			lpSolver.setMaxim();
		}
	}

	public void construireProg() {
		// On lit les variables qui interviennent dans le probleme
		lectureVariablesProgramme();

		// On affecte les variables LPSolve
		lectureVariablesLpsolve();

		// On crée les contraintes
		addContraintes();

		// On construit la fonction economique
		addObjectif();

		// Crée un fichier avec le probleme
		try {
			lpSolver.writeLp("model.lp");
		} catch (LpSolveException e) {
			e.printStackTrace();
		}
		lpSolver.setVerbose(LpSolve.IMPORTANT);

	}

	private void lectureVariablesLpsolve() {
		try {
			colno = new int[variablesProgramme.size()];
			row = new double[variablesProgramme.size()];

			// Cet objet va résoudre le probleme
			lpSolver = LpSolve.makeLp(0, variablesProgramme.size());
			if(lpSolver.getLp() == 0) {
				System.err.println("Impossible de construire le mod�le LPsolve");
				return;
			}

			// On nomme les variables
			int i = 1;
			for (Variable variable : variablesProgramme) {
				lpSolver.setColName(i, variable.getNomVecteur()+"\\" + variable.getIndex());
				lpSolver.setBounds(i, variable.getBorneMinReel(), variable.getBorneMaxReel());
				i++;
			}
		} catch (LpSolveException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void construireProgSansOpt() {
		// On doit trouver une solution admissible sans contrainte
		// On lit les variables qui interviennent dans le probleme
		lectureVariablesProgramme();

		// On affecte les variables LPSolve
		lectureVariablesLpsolve();

		// On crée les contraintes
		addContraintes();
					
		// Crée un fichier avec le probleme
		//lp.writeLp("./problems/model.lp");
		lpSolver.setVerbose(LpSolve.IMPORTANT);
	}

	private void lectureVariablesProgramme() {
		variablesProgramme = new Vector<Variable>();
		for (Vecteur vec : pl.getVecteurs()) {
			for (int i = 1; i <= vec.getTaille(); i++) {
				variablesProgramme.add(vec.getVar(i));
			}
		}
	}

	//@Override
	public Solution resoudreProgramme(ProgrammeLineaire p, boolean initial) {
		Solution solution = null;
		int retour = -1;
		Vector<Affectation<?>> affectations = null;

		// Construction du probleme
		this.pl = p;
		if (initial) {
			construireProgSansOpt();
		} else {
			construireProg();
		}

		// Resolution du probleme
		try {
			// Démarrage du chrono effectif de la résolution
			chrono.setPause(false);

			// Résolution
			retour = lpSolver.solve();

			// On arrete le chrono
			chrono.setPause(true);
			Lpsolve.dureeOptimisation += chrono.getTime();

			if (retour != LpSolve.OPTIMAL) {
				System.out.println("Solution non optimale");
				//return null;
			} else
				System.out.println("Solution optimale");

			// On récupère les résultats
			// Fonction objective
			System.out.println("Valeur de la solution: " + lpSolver.getObjective());

			// Assignation des variables
			row = new double[variablesProgramme.size()];
			lpSolver.getVariables(row);
			affectations = new Vector<Affectation<?>>();
			for (int j = 0; j < variablesProgramme.size(); j++) {
				System.out.println(lpSolver.getColName(j + 1) + ":: " + row[j]);

				System.out.println(" Variable : " 
						+ variablesProgramme.get(j).getNomVecteur() 
						+ variablesProgramme.get(j).getIndex() 
						+ " = " 
						+ row[j]);

				// Ajout d'une affectation
				affectations.add(variablesProgramme.get(j).creerAffectation(row[j]));
			}

			// Exploitation des resultats
			solution = new Solution(affectations);

			// TEMP affichage du chrono
			System.out.println("Chrono : " + Lpsolve.dureeOptimisation);

			// Terminaison de la résolution
			if (lpSolver.getLp() != 0)
				lpSolver.deleteLp();
		} catch (LpSolveException e) {
			e.printStackTrace();
		}

		return solution;
	}

	public static void main(String[] args) {
		/*
		 * max 143 x + 60 y
		 * 120 x + 210 y <= 15000
		 * 110 x + 30 y <= 4000
		 * x + y <= 75
		 */
//		FonctionEconomique fe = new FonctionEconomique(SensOptimisation.MAX);
//
//		Vecteur v = new Vecteur("x", 2);
//		VariableReelle v1 = new VariableReelle(v, 1, 0, Double.MAX_VALUE);
//		VariableReelle v2 = new VariableReelle(v, 2, 0, Double.MAX_VALUE);
//
//		fe.addTerme(v1, 143.0f);
//		fe.addTerme(v2, 60.0f);
//
//		ProgrammeLineaire pl = new ProgrammeLineaire(fe);
//		v.setVariable(v1, 1);
//		v.setVariable(v2, 2);
//		pl.addVecteur(v);
//
//		Contrainte c1 = new Contrainte("<=", 15000.0f);
//		Terme t1 = new Terme(120.0f, v1);
//		c1.addTerme(t1);
//		Terme t2 = new Terme(210.0f, v2);
//		c1.addTerme(t2);
//		pl.addContrainte(c1);
//		
//		Contrainte c2 = new Contrainte("<=", 4000.0f);
//		Terme t4 = new Terme(110.0f, v1);
//		c2.addTerme(t4);
//		Terme t5 = new Terme(30.0f, v2);
//		c2.addTerme(t5);
//		pl.addContrainte(c2);
//
//		Contrainte c3 = new Contrainte("<=", 75.0f);
//		Terme t6 = new Terme(1.0f, v1);
//		c3.addTerme(t6);
//		Terme t7 = new Terme(1.0f, v2);
//		c3.addTerme(t7);
//		pl.addContrainte(c3);
//
//		Lpsolve lpsolve = new Lpsolve();
//		lpsolve.resoudreProgramme(pl, true);
		
		FonctionEconomique fe = new FonctionEconomique(SensOptimisation.MAX);

		Vecteur vect1 = new Vecteur("W", 2);
		VariableReelle v1 = new VariableReelle(vect1, 1, 0, Double.MAX_VALUE);
		
		//Vecteur vect2 = new Vecteur("L", 1);
		VariableReelle v2 = new VariableReelle(vect1, 2, 0, 50);

		fe.addTerme(v1, 1.0f);

		ProgrammeLineaire pl = new ProgrammeLineaire(fe);
		vect1.setVariable(v1, 1);
		vect1.setVariable(v2, 2);
		//vect2.setVariable(v2, 1);
		pl.addVecteur(vect1);
		//pl.addVecteur(vect2);

		Contrainte c1 = new Contrainte("<=", 0.0f);
		Terme t1 = new Terme(1.0f, v1);
		c1.addTerme(t1);
		Terme t2 = new Terme(-2.0f, v2);
		c1.addTerme(t2);
		pl.addContrainte(c1);
		
		Contrainte c2 = new Contrainte("<=", 0.0f);
		Terme t4 = new Terme(1.0f, v1);
		c2.addTerme(t4);
		Terme t5 = new Terme(-3.0f, v2);
		c2.addTerme(t5);
		pl.addContrainte(c2);
		
		System.out.println(pl);
		
		Lpsolve.getInstance().resoudreProgramme(pl, false);
	}
}